<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ploy4J - 自动化部署平台</title>
    <meta name="description" content="Ploy4J - 简易Java应用部署工具">
    <meta name="_csrf" th:content="${_csrf?.token}"/>
    <meta name="_csrf_header" th:content="${_csrf != null ? _csrf.headerName : ''}"/>
    <link rel="icon" href="/img/favicon.ico" type="image/x-icon">
    <!-- Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Bootstrap 图标 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
    <!-- 应用样式 -->
    <style>
        /* 基本样式 */
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            margin: 0;
            background-color: #f0f2f5;
            color: #333;
            display: flex;
            flex-direction: column;
            min-height: 100vh;
        }
        
        /* 页眉样式 */
        .app-header {
            background-color: #fff;
            color: #333;
            padding: 0 24px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            height: 64px;
            border-bottom: 1px solid #e8e8e8;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
            z-index: 1000;
        }
        .header-left {
            display: flex;
            align-items: center;
        }
        .logo {
            font-size: 1.8em;
            font-weight: bold;
            color: #007bff;
            margin-right: 40px;
        }
        .main-nav ul {
            list-style-type: none;
            padding: 0;
            margin: 0;
            display: flex;
            align-items: center;
            height: 100%;
        }
        .main-nav ul li {
            margin-right: 5px;
        }
        .main-nav ul li a {
            color: #333;
            text-decoration: none;
            padding: 0 16px;
            height: 63px;
            display: flex;
            align-items: center;
            border-bottom: 2px solid transparent;
            transition: color 0.3s, border-bottom-color 0.3s;
        }
        .main-nav ul li a:hover,
        .main-nav ul li a.active {
            color: #007bff;
            border-bottom: 2px solid #007bff;
        }
        .header-right {
            display: flex;
            align-items: center;
        }
        .theme-toggle button, 
        .user-actions button {
            background: none;
            border: none;
            color: #555;
            font-size: 1.2em;
            cursor: pointer;
            margin-left: 16px;
            padding: 8px;
        }
        .theme-toggle button:hover, 
        .user-actions button:hover {
            color: #007bff;
        }
        .user-info {
            font-size: 0.9em;
            margin-right: 10px;
        }
        .user-info span {
            font-weight: bold;
        }
        .logout-form button {
            background-color: #ff4d4f;
            color: white;
            border: none;
            padding: 6px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9em;
            transition: background-color 0.3s;
        }
        .logout-form button:hover {
            background-color: #d9363e;
        }

        /* 内容区域 */
        .app-content {
            flex-grow: 1;
            padding: 24px;
        }

        /* 工具栏 */
        .page-toolbar {
            background-color: #fff;
            padding: 16px 24px;
            margin-bottom: 24px;
            border-radius: 4px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.04);
            display: flex;
            align-items: center;
            gap: 16px;
        }
        .page-toolbar input[type="text"], 
        .page-toolbar select {
            padding: 8px 12px;
            border: 1px solid #d9d9d9;
            border-radius: 4px;
            font-size: 14px;
        }
        .page-toolbar input[type="text"]::placeholder {
            color: #bfbfbf;
        }
        .page-toolbar button {
            padding: 8px 15px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        .page-toolbar button:hover {
            background-color: #0056b3;
        }

        /* 部署卡片区域 */
        .deployments-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 24px;
        }
        .deployment-card {
            background-color: #fff;
            border-radius: 4px;
            padding: 20px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.09);
        }
        .deployment-card .card-header {
            font-size: 1.1em;
            font-weight: 500;
            margin-bottom: 12px;
            color: #d9262c;
        }
        .deployment-card .card-branch-info {
            display: flex;
            align-items: center;
            font-size: 0.9em;
            color: #595959;
            margin-bottom: 8px;
        }
        .deployment-card .card-branch-info svg {
            margin-right: 6px;
            color: #007bff;
        }
        .deployment-card .card-time {
            font-size: 0.85em;
            color: #8c8c8c;
            margin-bottom: 16px;
        }
        .deployment-card .card-actions button {
            padding: 6px 12px;
            margin-right: 8px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9em;
            border: 1px solid transparent;
        }
        .deployment-card .card-actions .build-btn { background-color: #007bff; color:white; }
        .deployment-card .card-actions .build-btn:hover { background-color: #0056b3; }
        .deployment-card .card-actions .func-btn { background-color: #ffc107; color:#212529; border-color: #ffc107; }
        .deployment-card .card-actions .func-btn:hover { background-color: #e0a800; }
        .deployment-card .card-actions .details-btn { background-color: #28a745; color:white; }
        .deployment-card .card-actions .details-btn:hover { background-color: #1e7e34; }
        .deployment-card .card-actions .log-btn {
            background-color: #17a2b8; color:white; border-color: #17a2b8;
        }
        .deployment-card .card-actions .log-btn:hover { background-color: #138496; border-color: #117a8b;}

        .deployment-card .card-status {
            float: right;
            font-size: 0.9em;
            font-weight: bold;
            padding: 4px 8px;
            border-radius: 4px;
        }
        .deployment-card .card-status.fail {
            color: #f5222d;
            background-color: #fff1f0;
            border: 1px solid #ffa39e;
        }
        .deployment-card .card-status.success {
            color: #52c41a;
            background-color: #f6ffed;
            border: 1px solid #b7eb8f;
        }

        /* 空状态 */
        .empty-state {
            text-align: center;
            padding: 50px;
            background-color: #fff;
            border-radius: 4px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.04);
        }
        .empty-state h3 { font-size: 1.5em; color: #555; margin-bottom: 10px;}
        .empty-state p { font-size: 1em; color: #777; margin-bottom: 20px;}
        .empty-state .ant-btn-primary {
            background-color: #007bff; 
            color: white; 
            padding: 10px 20px; 
            text-decoration: none; 
            border-radius: 4px;
            transition: background-color 0.3s;
        }
        .empty-state .ant-btn-primary:hover { background-color: #0056b3;}

        /* 页脚 */
        footer {
            background-color: #f0f2f5;
            color: #8c8c8c;
            text-align: center;
            padding: 20px;
            margin-top: auto;
            font-size: 0.9em;
        }

        /* Goploy 风格组件 */
        .goploy-table th {
            background: #f5f7fa;
            color: #666;
            font-weight: 500;
            border-bottom: 1px solid #e4e7ed;
        }
        .goploy-table td {
            border-bottom: 1px solid #f0f0f0;
            vertical-align: middle;
        }
        .goploy-table tr {
            transition: background 0.2s;
        }
        .goploy-table tr:hover {
            background: #f6faff;
        }
        .goploy-table th, .goploy-table td {
            padding: 10px 8px;
        }
        .goploy-table .btn {
            border-radius: 6px;
            padding: 4px 10px;
            min-width: 0;
            font-size: 15px;
            margin-right: 4px;
        }
         .goploy-table .btn-outline-primary {
            color: #007bff;
            border-color: #007bff;
        }
        .goploy-table .btn-outline-primary:hover {
            background-color: #e7f3ff;
            color: #0056b3;
        }
        
        /* Bootstrap 兼容 */
        .bi {
            vertical-align: -.125em;
            font-size: 1.1em;
        }
        
        /* 加载指示器 */
        .content-loader {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 200px;
        }
        
        /* 响应式调整 */
        @media (max-width: 768px) {
            .app-header {
                padding: 0 10px;
                flex-direction: column;
                height: auto;
            }
            .header-left {
                width: 100%;
                justify-content: space-between;
                margin-bottom: 10px;
            }
            .main-nav {
                width: 100%;
                overflow-x: auto;
            }
            .main-nav ul {
                flex-wrap: nowrap;
            }
            .page-toolbar {
                flex-wrap: wrap;
            }
            .deployments-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
    <!-- Bootstrap 5 JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
    <!-- 顶部导航 -->
    <header class="app-header">
        <div class="header-left">
            <div class="logo"><span>Ploy4J</span></div>
            <nav class="main-nav">
                <ul>
                    <li><a id="nav-build-release" th:href="@{/}" class="active">构建发布</a></li>
                    <li><a id="nav-project-settings" th:href="@{/api/git-repo-configs/manage}">项目设置</a></li>
                    <li><a id="nav-servers" th:href="@{/api/server-configs/manage}">服务器</a></li>
                </ul>
            </nav>
        </div>
        <div class="header-right">
            <div class="theme-toggle">
                <button id="theme-toggle-btn" title="切换主题">☀️</button>
            </div>
            <div class="user-actions">
                <div class="user-info" sec:authorize="isAuthenticated()">
                    欢迎, <span sec:authentication="name" style="font-weight:bold;">User</span>
                </div>
                <form th:action="@{/logout}" method="post" class="logout-form" sec:authorize="isAuthenticated()">
                    <button type="submit">注销</button>
                </form>
            </div>
        </div>
    </header>

    <!-- 主内容区域 -->
    <main class="app-content" id="main-dynamic-content">
        <!-- 筛选工具栏 -->
        <div class="page-toolbar">
            <input type="text" placeholder="筛选...">
            <select>
                <option>所有项目</option>
            </select>
            <select>
                <option>所有状态</option>
            </select>
            <button>查询</button>
        </div>

        <!-- 配置提示 -->
        <div th:if="${!hasServerConfigs || !hasGitRepoConfigs}" class="config-prompt-container">
            <div class="empty-state" style="margin-bottom: 24px;">
                <h3>欢迎使用 Ploy4J!</h3>
                <p th:if="${!hasServerConfigs}">看起来您还没有配置任何服务器。请先 <a th:href="@{/api/server-configs/manage}">管理服务器配置</a>。</p>
                <p th:if="${!hasGitRepoConfigs}">看起来您还没有配置任何Git仓库。请先 <a th:href="@{/api/git-repo-configs/manage}">管理Git仓库配置</a>。</p>
                <p>完成配置后，您就可以开始自动化部署了。</p>
            </div>
        </div>

        <!-- 部署任务卡片 -->
        <div class="deployments-grid" th:if="${hasServerConfigs && !#lists.isEmpty(projects)}">
            <div class="deployment-card" th:each="project : ${projects}">
                <div class="card-header" th:text="${project.projectName}">项目名称</div>
                <div class="card-branch-info">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v12"></path><path d="M18 9l-6-6-6 6"></path><path d="M6 21v-6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6"></path></svg>
                    <span style="margin-left: 5px;" th:text="${project.defaultBranch}">main</span>
                </div>
                <p class="card-time">仓库 URL: <strong th:text="${project.repositoryUrl}">repo url</strong></p>
                <p class="card-time">更新时间: <strong class="card-time-updated-at" th:attr="data-raw-date=${project.lastSuccessfulDeploymentAt != null ? project.lastSuccessfulDeploymentAt.toString() : ''}" th:text="${project.lastSuccessfulDeploymentAt != null ? '加载中...' : '从未成功部署'}">update_time</strong></p>
                <div class="card-actions">
                    <button type="button" class="btn btn-sm btn-primary build-btn open-deploy-modal-btn"
                            th:attr="data-project-id=${project.id},
                                     data-project-name=${project.projectName},
                                     data-repo-url=${project.repositoryUrl},
                                     data-default-branch=${project.defaultBranch}">
                        <i class="bi bi-rocket-takeoff me-1"></i> 部署
                    </button>
                    <button type="button" class="btn btn-sm btn-info log-btn open-history-modal-btn" 
                            th:attr="data-project-id=${project.id}, 
                                     data-project-name=${project.projectName}"> 
                        <i class="bi bi-card-list me-1"></i> 部署记录
                    </button>
                </div>
            </div>
        </div>

        <!-- 空状态提示 -->
        <div th:if="${!hasServerConfigs || #lists.isEmpty(projects)}" class="empty-state" style="margin-top: 20px;">
            <div th:if="${hasServerConfigs && #lists.isEmpty(projects)}">
                <h3>暂无项目配置</h3>
                <p>您还没有配置任何Git仓库。请先 <a th:href="@{/api/git-repo-configs/manage}">管理Git仓库配置</a>。</p>
            </div>
        </div>
    </main>

    <!-- Deploy Config Modal -->
    <div class="modal fade" id="deployConfigModal" tabindex="-1" aria-labelledby="deployConfigModalLabel" aria-hidden="true">
        <div class="modal-dialog modal-lg modal-dialog-scrollable">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="deployConfigModalLabel">配置部署: <span id="deployModalProjectName"></span></h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button>
                </div>
                <div class="modal-body">
                    <input type="hidden" id="deployModalProjectId">
                    <input type="hidden" id="deployModalProjectRepoUrl">
                    
                    <div class="mb-3">
                        <label for="deployModalBranchName" class="form-label">部署分支</label>
                        <input type="text" class="form-control" id="deployModalBranchName" placeholder="例如: main 或 develop">
                        <div class="form-text">不填写则使用项目默认分支: <span id="deployModalDefaultBranchInfo"></span></div>
                    </div>

                    <div class="mb-3">
                        <h6>选择部署服务器 (单选)</h6>
                        <div id="deployModalServerListContainer" class="list-group" style="max-height: 200px; overflow-y: auto;">
                            <p>正在加载服务器列表...</p>
                        </div>
                    </div>
                    
                    <div class="mt-3">
                        <h6>部署日志</h6>
                        <pre id="deployModalLogOutput" class="bg-dark text-white p-2 rounded" style="min-height: 150px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word;">等待部署开始...</pre>
                    </div>
                    <div id="deployModalStatus" class="mt-2"></div>

                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
                    <button type="button" class="btn btn-primary" id="startDeployButtonModal">开始部署</button>
                </div>
            </div>
        </div>
    </div>

    <!-- Deployment History Modal -->
    <div class="modal fade" id="deploymentHistoryModal" tabindex="-1" aria-labelledby="deploymentHistoryModalLabel" aria-hidden="true">
        <div class="modal-dialog modal-xl modal-dialog-scrollable">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="deploymentHistoryModalLabel">部署记录: <span id="historyModalProjectName"></span></h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button>
                </div>
                <div class="modal-body">
                    <div id="deploymentHistoryTableContainer" class="table-responsive">
                        <p class="text-center">正在加载部署记录...</p>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
                </div>
            </div>
        </div>
    </div>

    <!-- Toast Container for Notifications -->
    <div class="toast-container position-fixed bottom-0 end-0 p-3">
        <div id="ployToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
            <div class="toast-header">
                <strong class="me-auto">Ploy4J 通知</strong>
                <small class="toast-time">刚刚</small>
                <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
            </div>
            <div class="toast-body">
                <!-- Toast message here -->
            </div>
        </div>
    </div>

    <!-- 页脚 -->
    <footer>
        Ploy4J &copy; <span th:text="${#dates.year(#dates.createNow())}">2024</span>. All rights reserved.
    </footer>

    <!-- 应用脚本 -->
    <script th:inline="javascript">
    /*<![CDATA[*/
    /**
     * Ploy4J App主脚本
     */
    const Ploy4J = {
        // 全局配置
        config: {
            defaultHash: '#build-release',
            activeNavClass: 'active',
            contentLoadDelay: 100,
            csrfHeader: document.querySelector('meta[name="_csrf_header"]').getAttribute('content'),
            csrfToken: document.querySelector('meta[name="_csrf"]').getAttribute('content')
        },
        
        // 元素缓存
        elements: {
            mainContent: document.getElementById('main-dynamic-content'),
            navLinks: document.querySelectorAll('.main-nav ul li a'),
            navBuildRelease: document.getElementById('nav-build-release'),
            navProjectSettings: document.getElementById('nav-project-settings'),
            navServers: document.getElementById('nav-servers'),
            themeToggle: document.getElementById('theme-toggle-btn'),
            // Deploy Config Modal elements
            deployModalElement: document.getElementById('deployConfigModal'),
            deployModalInstance: null, 
            deployModalProjectName: document.getElementById('deployModalProjectName'),
            deployModalProjectIdInput: document.getElementById('deployModalProjectId'),
            deployModalProjectRepoUrlInput: document.getElementById('deployModalProjectRepoUrl'),
            deployModalBranchNameInput: document.getElementById('deployModalBranchName'),
            deployModalDefaultBranchInfo: document.getElementById('deployModalDefaultBranchInfo'),
            deployModalServerListContainer: document.getElementById('deployModalServerListContainer'),
            deployModalLogOutput: document.getElementById('deployModalLogOutput'),
            deployModalStatus: document.getElementById('deployModalStatus'),
            startDeployButtonModal: document.getElementById('startDeployButtonModal'),
            // History Modal Elements
            deploymentHistoryModalElement: document.getElementById('deploymentHistoryModal'),
            deploymentHistoryModalInstance: null,
            historyModalProjectName: document.getElementById('historyModalProjectName'),
            deploymentHistoryTableContainer: document.getElementById('deploymentHistoryTableContainer'),

            ployToastElement: document.getElementById('ployToast'),
            ployToastInstance: null
        },
        
        // 存储初始内容
        initialContent: document.getElementById('main-dynamic-content').innerHTML,
        
        // 应用初始化
        init: function() {
            console.log('Ploy4J应用初始化...');
            this.bindEvents();
            this.handleInitialNavigation();
            // Initialize modal instances
            if (this.elements.deployModalElement) {
                this.elements.deployModalInstance = new bootstrap.Modal(this.elements.deployModalElement);
            }
            if (this.elements.deploymentHistoryModalElement) { 
                this.elements.deploymentHistoryModalInstance = new bootstrap.Modal(this.elements.deploymentHistoryModalElement);
            }
            if (this.elements.ployToastElement) {
                this.elements.ployToastInstance = new bootstrap.Toast(this.elements.ployToastElement);
            }
        },
        
        // 绑定事件
        bindEvents: function() {
            const self = this;
            
            if(this.elements.navBuildRelease) {
                this.elements.navBuildRelease.addEventListener('click', function(event) {
                    event.preventDefault();
                    self.loadHomeContent();
                });
            }
            
            if(this.elements.navProjectSettings) {
                this.elements.navProjectSettings.addEventListener('click', function(event) {
                    event.preventDefault();
                    self.loadContent(this.href, this);
                    window.location.hash = '#project-settings';
                });
            }
            
            if(this.elements.navServers) {
                this.elements.navServers.addEventListener('click', function(event) {
                    event.preventDefault();
                    self.loadContent(this.href, this);
                    window.location.hash = '#servers';
                });
            }
            
            if(this.elements.themeToggle) {
                this.elements.themeToggle.addEventListener('click', function() {
                    document.body.classList.toggle('dark-theme');
                    this.textContent = document.body.classList.contains('dark-theme') ? '🌙' : '☀️';
                });
            }

            document.querySelectorAll('.open-deploy-modal-btn').forEach(button => {
                button.addEventListener('click', function() {
                    const projectData = {
                        id: this.dataset.projectId,
                        name: this.dataset.projectName,
                        repoUrl: this.dataset.repoUrl,
                        defaultBranch: this.dataset.defaultBranch
                    };
                    self.showDeployModal(projectData);
                });
            });

            document.querySelectorAll('.open-history-modal-btn').forEach(button => {
                button.addEventListener('click', function() {
                    const projectId = this.dataset.projectId;
                    const projectName = this.dataset.projectName;
                    self.showDeploymentHistoryModal(projectId, projectName);
                });
            });

            if (this.elements.startDeployButtonModal) {
                this.elements.startDeployButtonModal.addEventListener('click', function() {
                    self.handleStartDeployment();
                });
            }
        },
        
        handleInitialNavigation: function() {
            const currentHash = window.location.hash;
            if (currentHash === '#project-settings' && this.elements.navProjectSettings) {
                this.elements.navProjectSettings.click();
            } else if (currentHash === '#servers' && this.elements.navServers) {
                this.elements.navServers.click();
            } else {
                if(this.elements.navBuildRelease) {
                    this.elements.navBuildRelease.classList.add(this.config.activeNavClass);
                }
                window.location.hash = this.config.defaultHash;
            }
        },
        
        loadHomeContent: function() {
            if (!this.elements.mainContent) return;
            this.updateNavState(this.elements.navBuildRelease);
            this.elements.mainContent.innerHTML = this.initialContent;
            this.bindDynamicHomeContentEvents();
            this.formatCardUpdateTimes();
            window.location.hash = this.config.defaultHash;
        },
        
        bindDynamicHomeContentEvents: function() {
            const self = this;
            document.querySelectorAll('.open-deploy-modal-btn').forEach(button => {
                button.removeEventListener('click', self.handleOpenDeployModal);
                button.addEventListener('click', self.handleOpenDeployModal.bind(self, button));
            });

            document.querySelectorAll('.open-history-modal-btn').forEach(button => {
                 button.removeEventListener('click', self.handleOpenHistoryModal);
                 button.addEventListener('click', self.handleOpenHistoryModal.bind(self, button));
            });
        },

        handleOpenDeployModal: function(buttonElement) {
            const projectData = {
                id: buttonElement.dataset.projectId,
                name: buttonElement.dataset.projectName,
                repoUrl: buttonElement.dataset.repoUrl,
                defaultBranch: buttonElement.dataset.defaultBranch
            };
            this.showDeployModal(projectData);
        },
        handleOpenHistoryModal: function(buttonElement){
            const projectId = buttonElement.dataset.projectId;
            const projectName = buttonElement.dataset.projectName;
            this.showDeploymentHistoryModal(projectId, projectName);
        },

        formatCardUpdateTimes: function() {
            document.querySelectorAll('.deployment-card .card-time-updated-at').forEach(timeEl => {
                const rawDate = timeEl.getAttribute('data-raw-date');
                if (rawDate) {
                    timeEl.textContent = Ploy4J.formatDateTimeToShanghai(rawDate);
                }
            });
        },
        
        loadContent: function(url, navElement) {
            if (!this.elements.mainContent) {
                console.error('主内容容器未找到');
                return;
            }
            this.updateNavState(navElement);
            this.showLoadingIndicator();
            fetch(url)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`网络请求失败: ${response.statusText}`);
                    }
                    return response.text();
                })
                .then(html => this.processLoadedContent(html, navElement))
                .catch(error => this.handleLoadError(error, url));
        },
        
        updateNavState: function(activeElement) {
            this.elements.navLinks.forEach(link => {
                link.classList.remove(this.config.activeNavClass);
            });
            if (activeElement) {
                activeElement.classList.add(this.config.activeNavClass);
            }
        },
        
        showLoadingIndicator: function() {
            this.elements.mainContent.innerHTML = `
                <div class="content-loader">
                    <div class="spinner-border text-primary" role="status">
                        <span class="visually-hidden">加载中...</span>
                    </div>
                </div>`;
        },
        
        processLoadedContent: function(html, navElement) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const newContentContainer = doc.querySelector('.ploy4j-page-content') || 
                                        doc.querySelector('div[layout\\:fragment="content"]') || 
                                        doc.querySelector('.container') || 
                                        doc.querySelector('.content') || 
                                        doc.body;
            const scriptElements = Array.from(doc.querySelectorAll('script'));
            if (newContentContainer) {
                this.elements.mainContent.innerHTML = newContentContainer.innerHTML;
            } else {
                throw new Error('无法找到内容容器');
            }
            scriptElements.forEach(script => {
                if (script.src) {
                    const newScript = document.createElement('script');
                    newScript.src = script.src;
                    document.body.appendChild(newScript);
                } else if (script.textContent.trim()) {
                    try { eval(script.textContent); } catch (e) { console.error('执行脚本时出错:', e); }
                }
            });
            this.initBootstrapComponents();
            this.initPageSpecificScripts(navElement);
        },
        
        initBootstrapComponents: function() {
            if (typeof bootstrap !== 'undefined') {
                document.querySelectorAll('.modal').forEach(modalEl => {
                    if (!bootstrap.Modal.getInstance(modalEl)) { new bootstrap.Modal(modalEl); }
                });
                document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(tooltipEl => { new bootstrap.Tooltip(tooltipEl); });
                document.querySelectorAll('.dropdown-toggle').forEach(dropdownEl => { new bootstrap.Dropdown(dropdownEl); });
            }
        },
        
        initPageSpecificScripts: function(navElement) {
            if (!navElement) return;
            setTimeout(() => {
                if (navElement.id === 'nav-servers') {
                    console.log('初始化服务器管理页面...');
                    if (typeof window.initServerManagement === 'function') {
                        window.initServerManagement();
                    } else {
                        console.warn('服务器管理初始化函数未找到');
                        const serverScript = document.createElement('script');
                        serverScript.textContent = document.querySelector('#server-management-content script')?.textContent || '';
                        if (serverScript.textContent) {
                            document.body.appendChild(serverScript);
                            if (typeof window.initServerManagement === 'function') {
                                window.initServerManagement();
                            }
                        }
                    }
                } else if (navElement.id === 'nav-project-settings') {
                    console.log('初始化Git仓库管理页面...');
                    if (typeof window.initGitRepoManagement === 'function') {
                        window.initGitRepoManagement();
                    } else {
                        console.warn('Git仓库管理初始化函数未找到');
                        const gitScript = document.createElement('script');
                        gitScript.textContent = document.querySelector('#git-repo-management-content script')?.textContent || '';
                        if (gitScript.textContent) {
                            document.body.appendChild(gitScript);
                            if (typeof window.initGitRepoManagement === 'function') {
                                window.initGitRepoManagement();
                            }
                        }
                    }
                } else if (navElement.id === 'nav-deployment') {
                    console.log('初始化部署表单页面...');
                    if (typeof window.initDeploymentForm === 'function') {
                        window.initDeploymentForm();
                    } else {
                        console.warn('部署表单初始化函数未找到');
                        const deployScript = document.createElement('script');
                        deployScript.textContent = document.querySelector('#deployment-form-content script')?.textContent || '';
                        if (deployScript.textContent) {
                            document.body.appendChild(deployScript);
                            if (typeof window.initDeploymentForm === 'function') {
                                window.initDeploymentForm();
                            }
                        }
                    }
                }
            }, 200);
        },
        
        handleLoadError: function(error, url) {
            console.error('内容加载失败:', error);
            this.elements.mainContent.innerHTML = `
                <div class="empty-state">
                    <h3>加载内容失败</h3>
                    <p>无法从 ${url} 加载内容</p>
                    <p class="text-danger">${error.message}</p>
                    <button class="btn btn-primary" onclick="location.reload()">刷新页面</button>
                </div>`;
            this.elements.deployModalInstance.show();
        },
        
        // --- DEPLOYMENT MODAL FUNCTIONS --- 
        currentDeploymentJobId: null,
        pollingIntervalId: null,
        
        showDeployModal: function(projectData) {
            this.elements.deployModalProjectName.textContent = projectData.name;
            this.elements.deployModalProjectIdInput.value = projectData.id;
            this.elements.deployModalProjectRepoUrlInput.value = projectData.repoUrl; 
            this.elements.deployModalBranchNameInput.value = projectData.defaultBranch || '';
            this.elements.deployModalDefaultBranchInfo.textContent = projectData.defaultBranch || '未设置';
            this.elements.deployModalLogOutput.innerHTML = '等待部署开始...';
            this.elements.deployModalStatus.innerHTML = '';
            this.elements.startDeployButtonModal.disabled = false;
            if (this.pollingIntervalId) {
                clearInterval(this.pollingIntervalId);
                this.pollingIntervalId = null;
            }
            this.currentDeploymentJobId = null;
            this.fetchServersForModal();
            this.elements.deployModalInstance.show();
        },
        
        fetchServersForModal: function() {
            const self = this;
            this.elements.deployModalServerListContainer.innerHTML = '<p class="text-muted">正在加载服务器列表...</p>';
            fetch('/api/server-configs', { method: 'GET', headers: { 'Accept': 'application/json' } })
            .then(response => {
                if (!response.ok) throw new Error('获取服务器列表失败: ' + response.statusText);
                return response.json();
            })
            .then(servers => { self.populateServersInModal(servers); })
            .catch(error => {
                console.error('获取服务器列表错误:', error);
                self.elements.deployModalServerListContainer.innerHTML = '<p class="text-danger">无法加载服务器列表: ' + error.message + '</p>';
                this.showToast('无法加载服务器列表', 'error');
            });
        },
        
        populateServersInModal: function(servers) {
            const container = this.elements.deployModalServerListContainer;
            container.innerHTML = ''; 
            if (!servers || servers.length === 0) {
                container.innerHTML = '<p class="text-warning">没有可用的服务器配置。</p>';
                this.elements.startDeployButtonModal.disabled = true; return;
            }
            this.elements.startDeployButtonModal.disabled = false;
            servers.forEach((server, index) => {
                const div = document.createElement('div'); div.classList.add('form-check');
                const input = document.createElement('input'); input.classList.add('form-check-input'); input.type = 'radio';
                input.name = 'deployModalServerSelection'; input.id = 'serverRadio-' + server.id; input.value = server.id;
                if (index === 0) { input.checked = true; }
                const label = document.createElement('label'); label.classList.add('form-check-label'); label.htmlFor = 'serverRadio-' + server.id;
                label.textContent = `${server.name} (${server.host}:${server.port})`;
                div.appendChild(input); div.appendChild(label); container.appendChild(div);
            });
        },
        
        handleStartDeployment: function() {
            const self = this;
            const gitRepoConfigId = this.elements.deployModalProjectIdInput.value;
            const selectedServerRadio = document.querySelector('input[name="deployModalServerSelection"]:checked');
            const serverConfigId = selectedServerRadio ? selectedServerRadio.value : null;
            let branchName = this.elements.deployModalBranchNameInput.value.trim();

            if (!gitRepoConfigId || !serverConfigId) { this.showToast(!gitRepoConfigId ? '项目ID未找到':'请选择部署服务器', 'error'); return; }
            this.elements.startDeployButtonModal.disabled = true;
            this.elements.deployModalLogOutput.innerHTML = '正在启动部署任务...';
            this.elements.deployModalStatus.innerHTML = '<span class="text-primary"><i class="bi bi-hourglass-split"></i> 初始化...</span>';
            const formData = new URLSearchParams();
            formData.append('gitRepoConfigId', gitRepoConfigId); formData.append('serverConfigId', serverConfigId);
            if (branchName) { formData.append('branchName', branchName); }
            fetch('/deploy/start', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', [this.config.csrfHeader]: this.config.csrfToken }, body: formData })
            .then(response => response.json())
            .then(data => {
                if (data.success && data.deploymentJobId) {
                    self.showToast('部署任务已启动 (ID: ' + data.deploymentJobId + ')', 'success');
                    self.currentDeploymentJobId = data.deploymentJobId;
                    this.appendDeployLog('部署任务 (ID: ' + data.deploymentJobId + ') 已成功创建并开始执行。\\n', true);
                    this.pollDeploymentStatus(data.deploymentJobId);
                } else { throw new Error(data.message || '启动部署失败'); }
            })
            .catch(error => {
                console.error('启动部署错误:', error);
                this.appendDeployLog('错误: ' + error.message + '\\n', false);
                this.elements.deployModalStatus.innerHTML = '<span class="text-danger"><i class="bi bi-x-circle-fill"></i> 启动失败</span>';
                this.showToast('启动部署失败: ' + error.message, 'error');
                this.elements.startDeployButtonModal.disabled = false;
            });
        },
        
        pollDeploymentStatus: function(jobId) {
            const self = this;
            if (this.pollingIntervalId) { clearInterval(this.pollingIntervalId); }
            this.pollingIntervalId = setInterval(() => {
                if (!self.currentDeploymentJobId || self.currentDeploymentJobId !== jobId) {
                    clearInterval(self.pollingIntervalId); return;
                }
                fetch(`/deploy/api/deployment-jobs/${jobId}`, { method: 'GET', headers: { 'Accept': 'application/json', [self.config.csrfHeader]: self.config.csrfToken } })
                .then(response => {
                    if (response.status === 404) {
                        clearInterval(self.pollingIntervalId);
                        this.appendDeployLog('错误: 部署任务未找到 (ID: ' + jobId + ')' + '\\n', false);
                        this.elements.deployModalStatus.innerHTML = '<span class="text-danger"><i class="bi bi-x-circle-fill"></i> 任务丢失</span>';
                        this.showToast('部署任务未找到，轮询停止。', 'error');
                        this.elements.startDeployButtonModal.disabled = false; return null;
                    }
                    if (!response.ok) throw new Error('获取部署状态失败: ' + response.statusText);
                    return response.json();
                })
                .then(jobDto => {
                    if (!jobDto) return;
                    let logOutputHtml = jobDto.deploymentOutput ? jobDto.deploymentOutput.replace(/\\n/g, '<br>') : '暂无日志输出。';
                    this.elements.deployModalLogOutput.innerHTML = logOutputHtml;
                    this.elements.deployModalLogOutput.scrollTop = this.elements.deployModalLogOutput.scrollHeight;
                    let statusHtml = ''; let isStillInProgress = true;
                    const spinner = '<div class="spinner-border spinner-border-sm text-primary me-1" role="status"></div>';
                    switch (jobDto.status) {
                        case 'PENDING': statusHtml = `<span class="text-secondary"><i class="bi bi-clock-history"></i> 待处理</span>`; break;
                        case 'GIT_PULLING': statusHtml = `<span class="text-primary">${spinner}正在拉取代码...</span>`; break;
                        case 'GIT_PULLED': statusHtml = `<span class="text-primary">${spinner}代码拉取完成, 准备构建...</span>`; break;
                        case 'BUILDING': statusHtml = `<span class="text-primary">${spinner}正在执行构建命令 (mvn/mvnw)...</span>`; break;
                        case 'BUILD_SUCCESS': statusHtml = `<span class="text-primary">${spinner}构建成功, 准备上传...</span>`; break;
                        case 'UPLOADING': statusHtml = `<span class="text-primary">${spinner}正在上传文件...</span>`; break;
                        case 'UPLOAD_SUCCESS': statusHtml = `<span class="text-primary">${spinner}文件上传成功, 准备远程操作...</span>`; break;
                        case 'KILLING_REMOTE': statusHtml = `<span class="text-primary">${spinner}正在停止旧进程...</span>`; break;
                        case 'KILL_REMOTE_SUCCESS': statusHtml = `<span class="text-primary">${spinner}旧进程停止成功, 准备启动新进程...</span>`; break;
                        case 'STARTING_REMOTE': statusHtml = `<span class="text-primary">${spinner}正在启动新进程...</span>`; break;
                        case 'REMOTE_STARTED': statusHtml = `<span class="text-primary">${spinner}远程进程已启动, 部署即将完成...</span>`; break;
                        case 'SUCCESS': statusHtml = '<span class="text-success"><i class="bi bi-check-circle-fill"></i> 部署成功</span>'; isStillInProgress = false; break;
                        case 'BUILD_FAILED': case 'UPLOAD_FAILED': case 'KILL_REMOTE_FAILED': case 'REMOTE_START_FAILED': case 'FAILED': 
                            statusHtml = `<span class="text-danger"><i class="bi bi-x-circle-fill"></i> 部署失败 (${jobDto.status})</span>`; isStillInProgress = false; break;
                        default: statusHtml = `<span class="text-muted">未知状态 (${jobDto.status || 'N/A'})</span>`; break;
                    }
                    this.elements.deployModalStatus.innerHTML = statusHtml;
                    if (!isStillInProgress) {
                        clearInterval(self.pollingIntervalId); self.pollingIntervalId = null;
                        self.elements.startDeployButtonModal.disabled = false;
                        this.showToast('部署' + (jobDto.status === 'SUCCESS' ? '成功' : '失败') + '!', jobDto.status === 'SUCCESS' ? 'success' : 'error');
                    }
                })
                .catch(error => {
                    console.error('轮询部署状态错误:', error);
                    this.appendDeployLog('轮询错误: ' + error.message + '\\n', false);
                    if(error.message.includes('Failed to fetch')) { 
                        clearInterval(self.pollingIntervalId);
                        this.elements.deployModalStatus.innerHTML = '<span class="text-danger"><i class="bi bi-wifi-off"></i> 网络错误，轮询停止</span>';
                        this.showToast('网络错误，轮询停止', 'error');
                        this.elements.startDeployButtonModal.disabled = false;
                    }
                });
            }, 3000); 
        },
        
        appendDeployLog: function(logMessage, clearPrevious = false) {
            const logHtml = logMessage.replace(/\\n/g, '<br>');
            if (clearPrevious) { this.elements.deployModalLogOutput.innerHTML = logHtml; } 
            else { this.elements.deployModalLogOutput.innerHTML += logHtml; }
            this.elements.deployModalLogOutput.scrollTop = this.elements.deployModalLogOutput.scrollHeight;
        },
        
        showToast: function(message, type = 'info') {
            const toastBody = this.elements.ployToastElement.querySelector('.toast-body');
            const toastHeader = this.elements.ployToastElement.querySelector(".toast-header");
            this.elements.ployToastElement.classList.remove('text-bg-success', 'text-bg-danger', 'text-bg-warning', 'text-bg-info');
            toastHeader.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info', 'text-white');
            switch(type) {
                case 'success': this.elements.ployToastElement.classList.add('text-bg-success'); toastHeader.classList.add('bg-success', 'text-white'); break;
                case 'error': this.elements.ployToastElement.classList.add('text-bg-danger'); toastHeader.classList.add('bg-danger', 'text-white'); break;
                case 'warning': this.elements.ployToastElement.classList.add('text-bg-warning'); toastHeader.classList.add('bg-warning'); break;
                default: this.elements.ployToastElement.classList.add('text-bg-info'); toastHeader.classList.add('bg-info', 'text-white');
            }
            toastBody.textContent = message;
            this.elements.ployToastInstance.show();
        },

        formatDateTimeToShanghai: function(dateTimeStr) {
            if (!dateTimeStr) return '-';
            try {
                const date = new Date(dateTimeStr); 
                return date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
            } catch (e) { console.error("Error formatting date:", dateTimeStr, e); return dateTimeStr; }
        },
        
        // --- DEPLOYMENT HISTORY MODAL FUNCTIONS ---
        showDeploymentHistoryModal: function(projectId, projectName) {
            const self = this;
            this.elements.historyModalProjectName.textContent = projectName || '未知项目';
            this.elements.deploymentHistoryTableContainer.innerHTML = '<p class="text-center"><div class="spinner-border spinner-border-sm" role="status"></div> 正在加载部署记录...</p>';
            this.elements.deploymentHistoryModalInstance.show();

            fetch(`/deploy/api/deployment-jobs?gitRepositoryConfigId=${projectId}`, {
                method: 'GET',
                headers: { 'Accept': 'application/json', [this.config.csrfHeader]: this.config.csrfToken }
            })
            .then(response => {
                if (!response.ok) { throw new Error(`获取部署记录失败: ${response.status} ${response.statusText}`); }
                return response.json();
            })
            .then(historyData => { self.populateDeploymentHistoryTable(historyData); })
            .catch(error => {
                console.error('获取项目部署记录错误:', error);
                self.elements.deploymentHistoryTableContainer.innerHTML = `<p class="text-danger text-center">无法加载部署记录: ${error.message}</p>`;
                this.showToast(`加载部署记录失败: ${error.message}`, 'error');
            });
        },

        populateDeploymentHistoryTable: function(jobs) {
            const container = this.elements.deploymentHistoryTableContainer;
            if (!jobs || jobs.length === 0) {
                container.innerHTML = '<p class="text-center text-muted">该项目暂无部署记录。</p>'; return;
            }
            let tableHtml = `
                <table class="table table-striped table-hover goploy-table" style="width: 100%;">
                    <thead><tr><th>ID</th><th>分支</th><th>服务器</th><th>状态</th><th>触发者</th><th>创建时间</th><th>完成时间</th><th>操作</th></tr></thead>
                    <tbody>`;
            jobs.forEach(job => {
                tableHtml += `
                    <tr>
                        <td>${job.id}</td>
                        <td>${job.branchToDeploy || '-'}</td>
                        <td>${job.serverConfigName || '-'}</td>
                        <td><span class="badge bg-${this.getJobStatusBadgeClass(job.status)}">${this.formatJobStatus(job.status)}</span></td>
                        <td>${job.triggeredBy || '-'}</td>
                        <td>${job.createdAt ? this.formatDateTimeToShanghai(job.createdAt) : '-'}</td>
                        <td>${job.completedAt ? this.formatDateTimeToShanghai(job.completedAt) : '-'}</td>
                        <td><button class="btn btn-sm btn-outline-primary view-log-btn" data-job-id="${job.id}" title="查看完整日志"><i class="bi bi-file-text"></i></button></td>
                    </tr>`;
            });
            tableHtml += `</tbody></table>`;
            container.innerHTML = tableHtml;
            container.querySelectorAll('.view-log-btn').forEach(button => {
                button.addEventListener('click', (event) => {
                    const jobId = event.currentTarget.dataset.jobId;
                    console.log("View full log for job ID: " + jobId + " (Not implemented yet in history modal)");
                    this.showToast("查看完整日志功能待实现 (Job ID: " + jobId + ")", 'info');
                });
            });
        },

        getJobStatusBadgeClass: function(status) {
            switch (status) {
                case 'SUCCESS': return 'success';
                case 'FAILED': case 'BUILD_FAILED': case 'UPLOAD_FAILED': case 'KILL_REMOTE_FAILED': case 'REMOTE_START_FAILED': return 'danger';
                case 'PENDING': return 'secondary';
                default: return 'primary'; 
            }
        },

        formatJobStatus: function(status) { 
            if (!status) return '未知';
            return status.replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase());
        }
    };

    // 页面加载完成后初始化应用
    document.addEventListener('DOMContentLoaded', function() {
        const serverConfig = /*[[${hasServerConfigs}]]*/ false;
        const gitRepoConfig = /*[[${hasGitRepoConfigs}]]*/ false;
        
        if (!serverConfig || !gitRepoConfig) {
            const deployButton = document.querySelector('a[href="@{/deploy}"]');
            if(deployButton) {
                deployButton.classList.add('disabled-link');
                deployButton.style.pointerEvents = 'none';
                deployButton.style.opacity = '0.6';
                deployButton.onclick = function(event) {
                    event.preventDefault();
                    Ploy4J.showToast('请先完成服务器和Git仓库配置才能新建发布！', 'warning');
                    return false;
                };
            }
        }
        
        Ploy4J.init();
        Ploy4J.formatCardUpdateTimes(); // Format card update times on initial load
    });
    /*]]>*/
    </script>
</body>
</html>

</rewritten_file>